(in-package :azala)

(defgeneric diff-list (operator x y dx)
  (:documentation "Expanded version of diff which works only on
lists of an operator and two operations"))

(defgeneric diff (expression dx)
  (:documentation "Differentiate an expression with respect to DX"))

(defmethod diff ((exp number) dx)
  (declare (ignore dx))
  0)

(defmethod diff ((exp symbol) dx)
  (if (eql exp dx)
      1
      0))

;; The derivative of e^x is e^x
(defmethod diff ((exp (eql '%e)) dx)
  exp)

(defmethod diff ((exp list) dx)
  (destructuring-bind (operator x y) (normalize exp)
    (simplify (diff-list operator x y dx))))

(defmacro defdiff (operator arguments &body body)
  "Define a differentiation rule"
  (let ((operator-symbol (gensym "operator")))
    `(defmethod diff-list ((,operator-symbol (eql ',operator))
			   ,@arguments)
      (simplify (progn ,@body)))))

(defdiff + (x y dx)
  (list '+
	(diff x dx)
	(diff y dx)))

(defdiff - (x y dx)
  (list '-
	(diff x dx)
	(diff y dx)))

(defdiff * (x y dx)
  `(+ (* ,(diff x dx) ,y)
      (* ,x ,(diff y dx))))

(defdiff / (x y dx)
  `(/ (- (* ,(diff x dx) ,y)
       (* ,x ,(diff y dx)))
    (expt ,y 2)))

(defdiff expt (base (power number) dx)
  `(* ,power (expt ,base (- ,power 1))))

(defdiff expt (base power dx)
  `(* (* ,(diff power dx)
         (log ,base %e))
      (expt ,base ,power)))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(defun diff-degree (exp dx degree)
  (loop with x = exp
	repeat degree
	do (setf x (diff x dx))
	finally (return x)))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Unary operations

(defmethod diff :around (expression dx)
  (if (and (listp expression)
	   (= (length expression) 2))
      (simplify (diff-unary (first expression) (second expression) dx))
      (call-next-method)))

(defgeneric diff-unary (operator x dx)
  (:documentation "Specialized diff function only for unary operators"))